package org.msb.accentis.gui.views;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import javax.swing.table.AbstractTableModel;

import org.msb.accentis.data.DataManagerFactory;
import org.msb.accentis.data.Database;
import org.msb.accentis.data.IDataManagerFactoryListener;
import org.msb.accentis.data.IObjectListener;
import org.msb.accentis.data.pojo.BaseObject;
import org.msb.accentis.data.pojo.NamedObject;
import org.msb.accentis.gui.Resources;

@SuppressWarnings("serial")
public abstract class BasicTableModel<T extends NamedObject> extends AbstractTableModel implements IDataManagerFactoryListener {
	
	private List<T> list = new ArrayList<T>();
	
	protected BasicTableModel() {
		DataManagerFactory.getInstance().addDataManagerFactoryListener(this);
	}
	
	@Override
	public int getRowCount() {
		return list.size();
	}

	protected abstract String[] getColumnNames();
	
	@Override
	public int getColumnCount() {
		return getColumnNames().length;
	}
	
	@Override
	public String getColumnName(int column) {
		return Resources.getInstance().getString(getResourcePrefix() + "table." + getColumnNames()[column] + "column.name");  //$NON-NLS-1$//$NON-NLS-2$
	}

	protected abstract String getResourcePrefix();
	
	@Override
	public Object getValueAt(int row, int column) {
		T object = list.get(row);
		
		return getValue(object, column);
	}
	
	public T getPojoAt(int row) {
		return list.get(row);
	}
	
	protected abstract Object getValue(T object, int column);

	void removePojos(int[] rows) {
		Arrays.sort(rows);

		for (int i = rows.length - 1; i >= 0; --i)
			DataManagerFactory.getInstance().getDatabase().getDataManager().remove(list.get(rows[i]));
	}
	
	@Override
	public void databaseChanged(Database database) {
		list.clear();
		
		if (null != database) {
			list.addAll(database.getDataManager().<T> runNamedQuery(getQueryName()));
			database.getObjectNotifier().addListener(getDataManagerListener());
		}
		
		fireTableDataChanged();
	}

	protected abstract String getQueryName();
	
	protected abstract Class<T> getPojoClass();

	private IObjectListener getDataManagerListener() {
		return new IObjectListener() {
			@Override
			public boolean listensToClass(Class<? extends BaseObject> klass) {
				return getPojoClass().isInstance(klass);
			}
			
			@SuppressWarnings("unchecked")
			@Override
			public <S extends BaseObject> void objectCreated(S object) {
				T castedObject = (T) object;
				int index = Collections.binarySearch(list, castedObject);
				if (0 <= index)
					return;

				index = -index - 1;
				list.add(index, castedObject);
				fireTableRowsInserted(index, index);
			}

			@SuppressWarnings("unchecked")
			@Override
			public <S extends BaseObject> void objectRemoved(S object) {
				T castedObject = (T) object;
				int index = Collections.binarySearch(list, castedObject);
				if (0 > index)
					return;

				list.remove(index);
				fireTableRowsDeleted(index, index);
			}

			@SuppressWarnings("unchecked")
			@Override
			public <S extends BaseObject> void objectSaved(S object) {
				T castedObject = (T) object;
				int preSortIndex = list.indexOf(castedObject);
				Collections.sort(list);
				int postSortIndex = Collections.binarySearch(list, castedObject);

				if (preSortIndex != postSortIndex) {
					fireTableRowsDeleted(preSortIndex, preSortIndex);

					if (postSortIndex > preSortIndex)
						postSortIndex--; // Account for the smaller row index having been removed.

					fireTableRowsInserted(postSortIndex, postSortIndex);
				} else
					fireTableRowsUpdated(preSortIndex, preSortIndex);
			}

			@Override
			public <S extends BaseObject> void objectRefreshed(S object) {
			}
		};
	}
}
